Release 10.1A: OpenEdge Development:
Progress Dynamics Advanced Development


Running a PLIP from client logic

A Progress Dynamics Persistent Library of Internal Procedures (PLIP) is simply a Progress 4GL procedure that is designed to hold business logic and run on the server side of a distributed application, to be invoked from other code either on the server or on the client side. Naturally, you want to avoid making calls from client code to the server whenever possible in order to maximize application performance. But sometimes it is necessary to make extra calls beyond those that the framework uses to populate the client-side objects and return data to the server. This section illustrates how you can use the Progress Dynamics Session Manager to invoke server-side logic that accesses the application database.

In this example, you want to allow the user to make a call back to the server to obtain the latest Credit information for a Customer during OrderLine maintenance. Again, whenever possible, this kind of information should always be brought over along with other information the framework objects are already handling for you, using a device such as calculated fields in an SDO. But for the sake of the example, we presume that this must indeed be an immediate call back to the server.

To run a Progress Dynamics PLIP:

  1. Build the business logic procedure itself. Select the New command in the AppBuilder menu, and select Structured PLIP as the object type. See OpenEdge Development: Progress Dynamics Basic Development for more information on the structure of the PLIP. In the Section Editor, add a new internal procedure called AvailCredit, which looks like this:
  2. PROCEDURE AvailCredit: 
    /*--------------------------------------------------------------------- 
      Purpose:      
      Parameters:  <none> 
      Notes:        
    ---------------------------------------------------------------------*/ 
    DEFINE INPUT  PARAMETER pcKeyType     AS CHARACTER  NO-UNDO. 
    DEFINE INPUT  PARAMETER piKeyNum      AS INTEGER    NO-UNDO. 
    DEFINE OUTPUT PARAMETER pdAvailCredit AS DECIMAL    NO-UNDO INIT ?. 
    IF pcKeyType = "OrderNum" THEN 
    DO: 
        FIND Order WHERE Order.OrderNum = piKeyNum NO-LOCK NO-ERROR. 
        IF AVAILABLE (Order) THEN 
            FIND Customer WHERE Customer.CustNum = Order.CustNum NO-LOCK. 
    END. 
    ELSE IF pcKeyType = "CustNum" THEN 
        FIND Customer WHERE Customer.CustNum = piKeyNum NO-LOCK NO-ERROR. 
    IF AVAILABLE (Customer) THEN 
        pdAvailCredit = Customer.CreditLimit. 
    END PROCEDURE. 
    

    This takes a pcKeyType parameter to tell it whether the key being passed in is a CustNum or an OrderNum, and then uses the key value itself to retrieve the appropriate Customer record. It returns Customer.CreditLimit as the output parameter. Again, real-life PLIPs will normally do more serious work that could not be returned in a simple field as part of an SDO.

  3. Save this PLIP in the oe directory (or wherever you are organizing your objects) as customerplip.p.
  4. Now you must build a window where you support Order and OrderLine maintenance. Any combination of objects that manages these tables will do, but the step describes the combination used for the present example. The example presumes that you have generated SDOs and dynamic viewers and browsers for the Sports2000 tables.

  5. Build an Order browse window as an independent window. (For more information on building a browse window, see OpenEdge Development: Progress Dynamics Basic Development .) Use the layout called rywinObjCont as the Container Template, substitute the SDO orderfullo for the template SDO, and substitute the dynamic browser orderfullb for the template browser. Select the dynamic browser in the layout grid, right- click to bring up the property sheet, and type orderfoldwin as the FolderWindowToLaunch property. This is the maintenance window that starts when the user double-clicks on a row in the browse or selects one of the update-related buttons in the toolbar:
  6. Save this container as orderbrowsewin.
  7. Create the Order Maintenance window. Create a new dependent window, which gets the OrderNum as input from the Order browse window. Use the folder window rywinFolder as the existing container to create this from.
  8. Select Page 1 in the Container Builder, which is part of the container template. This page layout is designed for a single static viewer. If you have a dynamic Order viewer you would like to use instead, you can unsubstitute the page layout rypagDynView as the template. Substitute the Order viewer (the example static viewer is orderstviewv.w, previously built in the AppBuilder) for the template viewer.
  9. Add a Page 2 to display and maintain OrderLine records. Add the dynamic OrderLine SDO orderlinfullo to this page, along with the dynamic browser orderlinfullb, and the dynamic viewer orderlinviewv.
  10. Select the orderlinfullo SDO in the grid, open the property sheet for it, and type ordernum,ordernum as the Foreign Fields. This will filter the OrderLine SDO for OrderLine records of the currently selected Order.
  11. Fill out the necessary data and update links between the OrderLine objects on Page 2 so that records are passed back and forth properly.
  12. Create a TableIO link from the StandardToolbar to the static Order viewer orderstviewv. This allows the toolbar on Page 0 to maintain the Order displayed on Page 1. Create another TableIO link from it to the OrderLine viewer orderlinviewv as well.
  13. Create a Data link from THIS-OBJECT (which represents the container itself) to the static Order viewer. This populates the viewer with data from the record selected in the browse window.
  14. Create an Update link from the static Order viewer to THIS-OBJECT. This will pass the update back to the Order SDO maintained in the browse window.
  15. Create a Data link from THIS-OBJECT to the OrderLine SDO orderlinfullo. This will pass the current OrderNum in to the OrderLine SDO, which, using the Foreign Fields information you entered, will filter the OrderLine records for the current Order.
  16. When you are done, your Links Maintenance window should look like this:

  17. Save this as orderfoldwin.
  18. Add a fill-in to the dynamic OrderLine viewer. Open the orderlinviewv viewer in the AppBuilder using the Open Object dialog box. Add the fill-in to the layout just as you would for a static viewer. Name it AvailCredit and give it a label of Available Credit and a format of >>>>9.99.
  19. Define a UI event for this new field using the dynamic property sheet from the AppBuilder to trigger the internal procedure inside the custom super procedure that will, in turn, execute the server-side code to retrieve the credit information.
  20. It might be a better user interface if there was a button for the user to press to retrieve the credit information, but to keep things simple, retrieve the information ON LEAVE OF the dynamic fill-in itself by continuing the procedure.

  21. Type an Event Name of LEAVE, type an Action Type of RUN, type an Action Target of SELF, and for the Event Action type the internal procedure name ShowAvailCredit. This defines a UI event that, ON LEAVE OF the dynamic fill-in, will run the procedure ShowAvailCredit in the TARGET-PROCEDURE.
  22. Save this new UI event.
  23. Create the custom super procedure for the OrderLine viewer by selecting New Structured Procedure in the AppBuilder. Use the Section Editor to create an internal procedure called ShowAvailCredit. This procedure first retrieves the lists of field names and field handles from its TARGET-PROCEDURE, as the earlier example did, as shown:
  24. {get AllFieldNames   cFieldNames}. 
      {get AllFieldHandles cFieldHandles}. 
    

    The next block of code illustrates how you can retrieve values not only from fields in the viewer, but from any other related object in the container. Because the Customer Number is the preferred key value to use to retrieve the Credit Limit, but that field is not normally displayed in an OrderLine viewer, the code attempts to locate an Order SDO with the customer number in it. So it retrieves first the data source of the current viewer, which is the OrderLine SDO, and then its data source, which should be the Order SDO if there is one, as shown:

    hDataSource = DYNAMIC-FUNCTION('getDataSource':U IN TARGET-PROCEDURE). 
      hDataSource = DYNAMIC-FUNCTION('getDataSource':U IN hDataSource). 
    

    Note that these two statements are effectively identical to this {get} include file syntax, as shown:

    {get DataSource hDataSource}. 
      {get DataSource hDataSource hDataSource}. 
    

    This version might look confusing because the handle is reused to retrieve the second data source from the first. The second statement amounts to this:

    • Get the value of the DataSource attribute and put it into the variable hDataSource. Use hDataSource itself as the handle of the object to query for its DataSource.
    • If this optional third argument to the {get} include file is not there, it uses the TARGET-PROCEDURE by default.
    • In some cases there is no Order SDO above the OrderLine SDO. Because you should always write code that is as general-purpose as possible, it is good to allow for any combination of objects in any custom code you write. So the block of code checks to see whether this second DataSource in fact returned a valid procedure handle. If it did, then it uses an SDO function to retrieve the value of the CustNum field for the current row, as shown:

      iKeyNum = INTEGER(DYNAMIC-FUNCTION 
                  ('columnStringValue':U IN hDataSource, "CustNum":U)). 
      

      It sets another variable to indicate that it retrieved the CustNum. But if that second handle is not valid, then the code tries another approach, instead retrieving the value of the Order Number field from the OrderLine viewer, using the field names and handles lists, as shown in the following example:

      ASSIGN ilookup = LOOKUP("OrderNum", cFieldNames) 
             hField = WIDGET-HANDLE(ENTRY(ilookup, cFieldHandles)) 
             iKeyNum = INTEGER(hField:SCREEN-VALUE) 
      

      It sets the other variable to indicate that what it retrieved is the OrderNum.

      Save this procedure and register it in the Repository. Then associate it with the dynamic OrderlLine viewer in the AppBuilder. To do this, open the OrderLine viewer, double-click on it to bring up its App Builder property sheet, and set the Custom ADD-SUPER-PROC field to orderlinsuper.p. Note that you do not specify the .p filename extension or the relative pathname because this information was placed in the Repository when you registered the procedure.

      This simplified example might seem a bit contrived, but it demonstrates a couple of basic principles that are important to keep in mind when you write this kind of code:

    • Always write your code to work in as many client object configurations as possible — Do not assume a particular combination of objects in the client container, and do not assume what the exact contents of those objects will be. This makes your code more flexible and reusable, and less prone to errors if other objects in the user interface change.
    • Retrieve information from other objects if you need to — Remember that you can get data from anywhere else on the client, as long as you are flexible about how you look for it. The link functions in each SmartObject will connect you to other objects, and various types of functions can retrieve information from those objects. Getting the object’s ContainerSource and then searching the container’s ContainerTargets can allow you to locate any other object in the same container window as the object you started from. If a search such as this saves you from an extra AppServer hit to retrieve data that might be elsewhere on the client, it is well worth it.

Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095